\chapter{Billing Customers}
\label{chap:billingapp}

This example is adapted from the first working Gantry app that did anything
useful.  It was written by fellow Gantry developer Tim Keefer, to track his
consulting invoices
and appears in a slightly modified form in Bigtop::Docs::Tutorial.  I've
updated the version here to our current notions of best
practices.\footnote{The version in the Bigtop distribution and the book
code tar ball v1.1 is even more current, but the differences are minor.
None of the alterations to the code shown here have changed, but some
of the other generated code is different.}

If you are like me, you work for a paycheck.  Perhaps once or twice a year
you do some side work that needs an invoice.  If so, you might do what I
do and type that invoice into a text editor and send it via email.  The
date on the email and a quick check of bank deposits is enough to track it.
But, if you have a few more side jobs, manual invoicing starts to break down.
Hence our case study for this chapter: a
billing\index{billing example} system for occasional
consultants or landlords with a few tenants who owe them for utilities.
For the rest of the chapter, I'll pretend
to be a high powered consultant who has too many clients to track by hand.

\section{A Billing Data Model}

As I asserted in Chapter \ref{chap:simpleex}, most web apps are database
front ends.  Our billing app will be no different.  So, let's begin
with the data
model\index{data model!billing example}\index{billing example!data model}.
There are five tables summarized in Table
\ref{tab:billmodel}.

\begin{table}
\begin{center}
\begin{tabular}{l|l}
Table Name          & Purpose                                             \\
\hline
\verb+customer+     & People who occasionally owe me money                \\
\verb+my_company+   & Names I call myself when doing business             \\
\verb+invoice+      & Bills customers receive in email and                \\
                    & eventually pay                                      \\
\verb+line_item+    & Tasks listed on invoices                            \\
\verb+status+       & A code table describing invoice statuses
\end{tabular}
\end{center}
\caption{Billing case study data model tables.}
\label{tab:billmodel}
\end{table}

Figure \ref{fig:billing_model} shows all the fields in these tables.
Foreign keys are shown with arrows.  The foreign key columns are all
named the same as the foreign table.

\begin{figure}
\begin{center}
\includegraphics[width=4in]{billing_model}
\end{center}
\caption{Billing application data model.}
\label{fig:billing_model}
\end{figure}

\section{Bigtop File for Billing App}
\label{sec:billingbigtop}

While I could walk you through using bigtop and tentmaker to build this
data model, that wouldn't add much to previous chapters, see for example
Chapter \ref{chap:simpleex}.  Instead, I'll show you the finished Bigtop
file I made with bigtop, tentmaker, and my favorite editor.  While tentmaker
is great for getting started, it is often easier to move to a text editor to
fill in large chunks of repetitive descriptions.  You can examine the
finished result in the Bigtop source distribution, where it lives in
\verb+examples/billing.bigtop+.  There is also a finished version of the
application in the \verb+Billing-Finished+ subdirectory of the examples
directory.  I've
also included the full bigtop file here, to aid our discussion.  The one
below does have some formatting variations to accommodate the shape of
this book, but they are not semantically important.  Here it is, over
300 lines of
it\index{billing example!bigtop file}\index{bigtop!billing example source}
(note that this file is updated from the one in version 1.0 of this book, some
un-numbered lines have been added and one conf option has
been commented out, none of that affects the discussion here, but it
should make it easier to work on and deploy the app):

%billing_bigtop.numbered
{\footnotesize
\begin{verbatim}
  1  config {
  2      engine          CGI;
  3     template_engine TT;
  4     Init            Std             {}
  5     CGI             Gantry          {
  6         with_server 1;
  7#        gen_root    1;
  8         flex_db     1;
            gantry_conf 1;
  9     }
 10     SQL             Postgres        {}
 11     SQL             MySQL           {}
 12     SQL             SQLite          {}
 13     Control         Gantry          { dbix 1; }
 14     Model           GantryDBIxClass {}
 15     SiteLook        GantryDefault   {}
        Conf            Gantry          {
            instance billing;
            gen_root 1;
 16 }
 17 app Billing {
 18     authors `Phil Crow`, `Tim Keefer`;
 19     config {
 20         dbconn           `dbi:SQLite:dbname=app.db`
 21                          => no_accessor;
 22         template_wrapper `genwrapper.tt`  => no_accessor;
 23     }
 24     table    my_company      {
 25         foreign_display `%name`;
 26 
 27         field id { is int4, primary_key, auto; }
 28         field name {
 29             is             varchar;
 30             label          Name;
 31             html_form_type text;
 32         }
 33         field address {
 34             is             varchar;
 35             label          Address;
 36             html_form_type text;
 37         }
 38         field city {
 39             is             varchar;
 40             label          City;
 41             html_form_type text;
 42         }
 43         field state {
 44             is             varchar;
 45             label          State;
 46             html_form_type text;
 47         }
 48         field zip {
 49             is             varchar;
 50             label          Zip;
 51             html_form_type text;
 52         }
 53         field description {
 54             is                 varchar;
 55             label              Description;
 56             html_form_type     text;
 57             html_form_optional 1;
 58         }
 59         field contact_name  {
 60             is                 varchar;
 61             label              `Contact Name`;
 62             html_form_type     text;
 63         }
 64         field contact_email {
 65             is                 varchar;
 66             label              `Contact Email`;
 67             html_form_type     text;
 68         }
 69         field contact_phone {
 70             is                 varchar;
 71             label              `Contact Phone`;
 72             html_form_type     text;
 73         }
 74         data
 75             name => `Crow Motors`,
 76             address => `12 E. Main`,
 77             city => `Paxton`,
 78             state => `NE`,
 79             zip => 69155,
 80             description => `Car and Implement...`,
 81             contact_name => `EJ`,
 82             contact_email => `ej@example.com`,
 83             contact_phone => `1-800-CROW-MOT`;
 84     }
 85     table    customer         {
 86         foreign_display `%name`;
 87 
 88         field id { is int4, primary_key, auto; }
 89         field name {
 90             is             varchar;
 91             label          Name;
 92             html_form_type text;
 93         }
 94         field address {
 95             is             varchar;
 96             label          Address;
 97             html_form_type text;
 98         }
 99         field city {
100             is             varchar;
101             label          City;
102             html_form_type text;
103         }
104         field state {
105             is             varchar;
106             label          State;
107             html_form_type text;
108         }
109         field zip {
110             is             varchar;
111             label          Zip;
112             html_form_type text;
113         }
114         field description {
115             is                 varchar;
116             label              Description;
117             html_form_type     text;
118             html_form_optional 1;
119         }
120         field contact_name  {
121             is                 varchar;
122             label              `Contact Name`;
123             html_form_type     text;
124             html_form_optional 1;
125         }
126         field contact_email {
127             is                 varchar;
128             label              `Contact Email`;
129             html_form_type     text;
130             html_form_optional 1;
131         }
132         field contact_phone {
133             is                 varchar;
134             label              `Contact Phone`;
135             html_form_type     text;
136             html_form_optional 1;
137         }
138         data
139             name => `Groover Nordqvist`,
140             address => `502 E. Third`,
141             city => `Paxton`,
142             state => `NE`,
143             zip => 69155,
144             description => `Prime Customer`,
145             contact_name => `Groover`,
146             contact_email => `gnordqvist@example.com`,
147             contact_phone => `Unlisted`;
148     }
149     table    line_item        {
150         foreign_display `%name`;
151 
152         field id { is int4, primary_key, auto; }
153         field due_date {
154             is               date;
155             label            `Due Date`;
156             date_select_text Select;
157             html_form_type   text;
158         }
159         field name {
160             is               varchar;
161             label            Name;
162             html_form_type   text;
163         }
164         field invoice {
165             is                 int4;
166             label              `Invoice Number`;
167             refers_to          invoice;
168             html_form_type     select;
169         }
170         field hours {
171             is                 int4;
172             label              Hours;
173             html_form_type     text;
174         }
175         field charge_per_hour {
176             is                 int4;
177             label              Rate;
178             html_form_type     text;
179         }
180         field notes {
181             is                 text;
182             label              `Notes to Customer`;
183             html_form_type     textarea;
184             html_form_optional 1;
185             html_form_rows     4;
186             html_form_cols     50;
187         }
188         field description {
189             is                 text;
190             label              `Notes to Self`;
191             html_form_type     textarea;
192             html_form_optional 1;
193             html_form_rows     4;
194             html_form_cols     50;
195         }
196     }
197     table    invoice          {
198         foreign_display `%number`;
199 
200         field id { is int4, primary_key, auto; }
201         field number {
202             is                 varchar;
203             label              Number;
204             html_form_type     text;
205         }
206         field status {
207             is                 int4;
208             label              Status;
209             refers_to          status;
210             html_form_type     select;
211         }
212         field sent {
213             is                 date;
214             label              `Sent On`;
215             date_select_text   `Popup Calendar`;
216             html_form_type     text;
217             html_form_optional 1;
218         }
219         field paid {
220             is                 date;
221             label              `Paid On`;
222             date_select_text   `Popup Calendar`;
223             html_form_type     text;
224             html_form_optional 1;
225         }
226         field my_company {
227             is                 int4;
228             label              `My Company`;
229             refers_to          my_company;
230             html_form_type     select;
231         }
232         field customer {
233             is                 int4;
234             label              Customer;
235             refers_to          customer;
236             html_form_type     select;
237         }
238         field notes {
239             is                 text;
240             label              `Notes to Customer`;
241             html_form_type     textarea;
242             html_form_optional 1;
243             html_form_rows     4;
244             html_form_cols     50;
245         }
246         field description {
247             is                 text;
248             label              `Notes to Self`;
249             html_form_type     textarea;
250             html_form_optional 1;
251             html_form_rows     4;
252             html_form_cols     50;
253         }
254     }
255     table    status            {
256         foreign_display `%name: %description`;
257 
258         field id { is int4, primary_key, auto; }
259         field name {
260             is             varchar;
261             label          Name;
262             html_form_type text;
263         }
264         field description {
265             is             varchar;
266             label          Description;
267             html_form_type text;
268         }
269 
270         data
271             name        => `Working`,
272             description => `Work in Progress, NOT Billed`;
273         data
274             name        => `Sent`,
275             description => `Mailed to Customer`;
276         data
277             name        => `Paid`,
278             description => `Payment Received`;
279     }
280     controller Status is AutoCRUD {
281         controls_table   status;
282         rel_location     status;
283         text_description status;
284         page_link_label  Status;
285         method do_main is main_listing {
286             title            `Status`;
287             cols             name;
288             header_options   Add;
289             row_options      Edit, Delete;
290         }
291         method form is AutoCRUD_form {
292             form_name        status;
293             fields           name, description;
294             extra_keys
295                 legend     =>
296                 `$self->path_info =~ /edit/i ? 'Edit'
297                                              : 'Add'`;
298         }
299     }
300     controller Company is AutoCRUD {
301         controls_table   my_company;
302         rel_location     company;
303         text_description company;
304         page_link_label  Companies;
305         method do_main is main_listing {
306             title            `My Companies`;
307             cols             name, contact_phone;
308             header_options   Add;
309             row_options      Edit, Delete;
310         }
311         method form is AutoCRUD_form {
312             form_name        company;
313             all_fields_but   id;
314             extra_keys
315                 legend     =>
316                 `$self->path_info =~ /edit/i ? 'Edit'
317                                              : 'Add'`;
318         }
319     }
320     controller Customer is AutoCRUD {
321         controls_table   customer;
322         rel_location     customer;
323         text_description customer;
324         page_link_label  Customers;
325         method do_main is main_listing {
326             title            `Customers`;
327             cols             name,
328                              contact_name,
329                              contact_phone;
330             header_options   Add;
331             row_options      Edit, Delete;
332         }
333         method form is AutoCRUD_form {
334             form_name        customer;
335             all_fields_but   id;
336             extra_keys
337                 legend     =>
338                 `$self->path_info =~ /edit/i ? 'Edit'
339                                              : 'Add'`;
340         }
341     }
342     controller LineItem is AutoCRUD {
343         controls_table   line_item;
344         rel_location     lineitem;
345         uses             Gantry::Plugins::Calendar;
346         text_description `line item`;
347         page_link_label  `Line Items`;
348         method do_main is main_listing {
349             title            `Line Items`;
350             cols             name, invoice, due_date;
351             header_options   Add;
352             row_options      Edit, Delete;
353         }
354         method form is AutoCRUD_form {
355             form_name        line_item;
356             all_fields_but   id;
357             extra_keys
358                 legend     =>
359                 `$self->path_info =~ /edit/i ? 'Edit'
360                                              : 'Add'`,
361                 javascript =>
362                 `$self->calendar_month_js( 'line_item' )`;
363         }
364     }
365     controller Invoice is AutoCRUD {
366         controls_table   invoice;
367         rel_location     invoice;
368         uses             Gantry::Plugins::Calendar;
369         text_description invoice;
370         page_link_label  Invoices;
371         method do_pdf   is stub {
372             extra_args   `$id`;
373         }
374         method do_main is main_listing {
375             title            `Invoices`;
376             cols             number, customer, status;
377             header_options   Add;
378             row_options
379                 Tasks => `"/lineitem/main/$id"`,
380                 PDF,
381                 Edit,
382                 Delete;
383         }
384         method form is AutoCRUD_form {
385             form_name        invoice;
386             all_fields_but   id;
387             extra_keys
388                 legend     =>
389                 `$self->path_info =~ /edit/i ? 'Edit'
390                                              : 'Add'`,
391                 javascript =>
392                 `$self->calendar_month_js( 'invoice' )`;
393         }
394     }
395 }
\end{verbatim}
}

I should point out one bit of odd syntax in Bigtop.  It uses backquotes,
also called backticks, for all of its quoting.  My reason for this choice
is that web applications should not shell out, and therefore have no
reason to use backticks in Perl.  Using them in bigtop makes it easier
to enclose other quotes you might need in your app.  See for example
the form legend on lines 388-390 in the above bigtop listing.

There is one notable feature of this bigtop file. The \verb+status+,
\verb+customer+ and \verb+my_company+ tables use
\verb+data+ statements\index{data statements}\index{table!data statements}.  In
\verb+status+ (lines 270-278) they provide the actual data for the table.
Users will be
able to add, edit, and delete in the table, but these are meant to be
usable as a production starting point.  The other two use \verb+data+
statements to supply test data (lines 74-83 and 138-147).
This softens the blow of discarding a database during
development.  Normally I would remove the data statements before building
the final schema files.  But even when I forget, the pain is limited to
deleting two rows from the production database.

You can now manage data statements with tentmaker.  Whether you change
data statements in tentmaker or not, it will canonicalize the whitespace
to the format shown in lines 74-83.

Each data statement begins with \verb+data+, then has a comma separated
list of field name/value pairs, where the field names and values are separated
by \verb+=>+.  The values must be properly quoted.\footnote{using backticks}
The statement ends with
a semi-colon.  You may use as many data statements as you like.
Each one becomes a separate INSERT INTO statement.

Turning the above into a working
app\index{bigtop!--create or -c}\index{--create or -c bigtop flag}
is as simple as the example in Chapter
\ref{chap:simpleex}:

{\footnotesize
\begin{verbatim}
bigtop --create billing.bigtop all
cd Apps-Billing
sqlite app.db < docs/schema.sqlite
./app.server
\end{verbatim}
}

Note the \verb+--create+ flag.\begin{footnote}
which you may abbreviate as \verb+-c+\end{footnote}  It makes a brand new
applications from existing Bigtop files.

\section{Finishing the Bills}

Of course, the app so far is generic.  It does not know how to make PDF
invoices or even how to display the tasks associated with an invoice, but
it does all the other tedious parts.
So, let's finish it.  There are only a few remaining pieces, both start in
the Invoice controller.

\subsection{Showing Tasks for One Invoice}

First, let's link invoices\index{invoices, billing example}
to their tasks\index{tasks!billing example}.
Look at the Invoice\index{Invoice controller}
controller in the bigtop source above (it begins on line 365).
Its \verb+row_options+ include Tasks with an
explicit URL.  This becomes the following in the generated \verb+do_main+:

{\footnotesize
\begin{verbatim}
        my @row_options => (
            {
                text => 'Tasks',
                link => "/lineitem/main",
                type => 'retrieve',
            },
        #...
\end{verbatim}
}

It sets up a call to \verb+Billing::LineItem->do_main+.  All row options
are passed the invoice number\footnote{this happens in
Gantry::Utils::TablePerms,
but that is beside the point}.  Billing::LineItem uses that to limit the
line items displayed.
For that to work, we need to modify the \verb+do_main+ Bigtop generated
for us in the \verb+LineItem+ controller.  The generated one is almost right.

To find the code, I need to tell you a little bit more about how Bigtop
works.  When it makes a controller, it actually makes two Perl modules.
Both have the same name.  Bigtop puts the
stub\index{stub, bigtop}\index{bigtop!stub} in the usual place.  In our
case, that place is \verb+Billing/LineItem.pm+\footnote{forward slashes
are used on Unix, your slashes may be backslashes} under the \verb+lib+
directory.  The other piece is
the gen module, which Bigtop places in the
GEN\index{bigtop!GEN}
\index{GEN, bigtop}
subdirectory, so the other
piece is:
{\footnotesize
\begin{verbatim}
lib/Billing/GEN/LineItem.pm
\end{verbatim}
}

Since the generated version is so close to what I want, I copied it from
generated module into the stub module\index{LineItem controller}.
There is no need to modify the bigtop file
or remove it from the GEN module.  Stub controllers inherit from their
generated modules, so we can
override\index{override!GEN method}
the method.
Below, we will see another approach to modifying a generated \verb+do_main+.

In order for my new \verb+do_main+ to work, I need to use a couple
of additional modules at the top of the \verb+LineItem+ controller:

{\footnotesize
\begin{verbatim}
use Billing::Model::line_item qw( $LINE_ITEM );
use Billing::Model::invoice   qw( $INVOICE   );
\end{verbatim}
}

Actually, the first of those was already present, but I removed some
whitespace to make it prettier.
Here is the new \verb+do_main+.  Lines marked with \verb+*+ are new, those
with \verb+M+ are modified:

{\footnotesize
\begin{verbatim}
  1 #------------------------------------------------------
  2 # $self->do_main( [ $invoice_id ] )                 # M
  3 #------------------------------------------------------
  4 sub do_main {
  5     my ( $self, $invoice_id ) = @_;                 # M
  6 
  7     my $only_one_invoice = 0;                       # *
  8     my $title            = 'Line Items';            # *
  9     if ( defined $invoice_id ) {                    # *
 10         $only_one_invoice = 1;                      # *
 11         my $invoice = $INVOICE->gfind(              # *
 12             $self, $invoice_id                      # *
 13         );                                          # *
 14         $title .= ' for Invoice '                   # *
 15                .   $invoice->foreign_display();     # *
 16     }
 17 
 18     $self->stash->view->template( 'results.tt' );
 19     $self->stash->view->title( 'Line Items' );
 20
   ... unchanged lines omitted
100 
101     ROW:
102     foreach my $row ( @rows ) {
103         last ROW if $perm_obj->hide_all_data;
104 
105         my $id = $row->id;
106         my $invoice = ( $row->invoice )
107                 ? $row->invoice->foreign_display()
108                 : '';
109 
110         next ROW if ( $only_one_invoice             # *
111                         and                         # *
112                       $id != $invoice_id );         # *
113 
114         push(
115             @{ $retval->{rows} }, {
116                 orm_row => $row,
117                 data => [
118                     $row->name,
119                     $invoice,
120                     $row->due_date,
121                 ],
122                 options =>
123                     $perm_obj->real_row_options( $row ),
124             }
125         );
126     }
127
   ... unchanged lines omitted
140
141     $self->stash->view->data( $retval );
142 } # END do_main
\end{verbatim}
}

The main changes are at the top.  First, on line 2, I altered the opening
comment to show future coders that invoice id is an optional parameter.
Line 5 makes the comment true by collecting the optional invoice id.
Then, on lines 9-16, I check the value.  If it is true, I
set a flag, get the invoice with that id, and append its name to the title.
Notice that I used \verb+gfind+\index{gfind@\verb+gfind+ ORM sugar
method}\index{ORM!sugar!gfind@\verb+gfind+}
on lines 11-13.  This is one of the sugar
methods provided by Gantry's DBIx::Class subclass, Gantry::Utils::DBIxClass,
whose POD explains how to use all the sugar methods.

I made only one other change: I added a test on lines 110-112 which skips
rows for other invoices, if we care.  Remember that users may hit the page
directly.  Then they presumably want to see all the tasks.

\subsection{How Many Tasks Must I Do?}

For shear fun, I decided that the Tasks link should indicate how many
tasks\index{tasks!how many}\index{billing example!task counting}
are associated with the invoice.  While this is clearly not necessary,
it does show some additional techniques, further I've done it for other apps
to the delight of my users.
This change also involves alterations to the generated \verb+do_main+
\verb+do_main+\index{override!with SUPER
dispatch},\index{do main@\verb+do_main+!custom}
but the changes are less dramatic.
The new \verb+Invoice->do_main+ dispatches to the
parent\index{dispatching to parent method} version which is inherited
from the generated module, then modifies what that method returns:

{\footnotesize
\begin{verbatim}
 1 #------------------------------------------------------------
 2 # $self->do_main(  )
 3 #------------------------------------------------------------
 4 sub do_main {
 5     my $self = shift;
 6 
 7     $self->SUPER::do_main();
 8 
 9     my $rows = $self->stash->view->data()->{ rows };
10     my $line_item_counts = $LINE_ITEM->get_count( $self );
11 
12     foreach my $row ( @{ $rows } ) {
13         my $task_option = $row->{ options }[0];
14 
15         my $id          = ( split /\//,
16                             $task_option->{ link } )[-1];
17 
18         my $count       = $line_item_counts->{ $id } || 0;
19 
20         $task_option->{ text } .= " ($count)";
21     }
22 }
\end{verbatim}
}

On line 7, I use Perl's \verb+SUPER+ to call \verb+do_main+ in
\verb+GEN/Invoice+.  Use of \verb+SUPER+ is explained in
\verb+perldoc perltoot+ among other places.
When the \verb+SUPER+\index{SUPER} version finishes, the template
data\index{template!data}\index{stash!template data}
is in:
{\footnotesize
\begin{verbatim}
$self->stash->view->data()
\end{verbatim}
}
\noindent
In particular, we are interested in the
\verb+rows+\index{rows!array in main listing data}
hash key.  For each row, we want to add a count of tasks after
the link text `Tasks'.  First, we need to know how many tasks there are for
this invoice.  The neatest way to find out is to ask the model, as
I did on line 10.  Later we
will see how the model calculates these counts.  For now, it is enough to know
that they come back in a hash reference keyed by invoice row id, when
we call \verb+get_count+ on the line item model, whose alias is
\verb+$LINE_ITEM+.

Once we have the data from the parent (GEN) module and a count of all the
tasks collated by invoice, we are ready to walk through the output rows.
Each row is itself a hash.  The key we care about, is
`options.'\index{options!in main listing rows}
It stores the links and locations for the right hand column of the
HTML table delivered to browsers.  Users click these links  to navigate.
Usually there are only Edit and Delete links.  Recall
from above that this app also has Tasks and PDF.

The Tasks link\index{row options}\index{tasks!row option}
comes first, so line 13 stores the option from the zeroth element of the
row array.  We are interested
in the text, but we must retrieve the row id from the link.  That id is
the last element of the URL, hence the split on slashes using only the item
at index -1 on lines 15-16.  With the id, we can look up the task count
for the invoice on line 18.  Finally, we can append the the count (in
parentheses) to the link text on line 20.

The remaining piece counts the tasks\index{tasks!counting},
grouping them by invoice id.  It is
implemented in a method in the model stub.  Thus, the following code goes
in \verb+lib/Billing/Model/line_item.pm+:

{\footnotesize
\begin{verbatim}
sub get_count {
    my $class       = shift;
    my $gantry_site = shift;

    my %retval;

    my @line_items = $class->gsearch( $gantry_site );

    foreach my $line_item ( @line_items ) {
        $retval{ $line_item->invoice->id }++;
    }

    return \%retval;
}
\end{verbatim}
}

Gantry's DBIx::Class model base class provides
\verb+gsearch+\index{gsearch@\verb+gsearch+ ORM sugar
method}\index{ORM!sugar!gsearch@\verb+gsearch+}
and other
sugar methods to save some typing.  This one does a search on the table.
Since no args were provided beyond the site object (which is needed to
provide the schema), all rows are returned.

A quick loop through the line items collects the counts in a hash, which
is returned as a hash reference.

\subsection{Printable Invoices}

With a relatively easy way for users to examine task/invoice connections,
it is time to actually make invoices.  The full version of this
example, that ships with Bigtop
has a PDF\index{PDF in billing example} generator in it.\begin{footnote}
The finished example is in the \verb+examples/Billing-Finished+ subdirectory
of the Bigtop distribution.  The invoice module lives in its \verb+lib+
subdirectory as \verb+lib/Bigtop/Examples/Billing/Invoice.pm+.
\end{footnote}
I'm leaving that out here, because it
is large and repetitive.  Instead, I'm going to make a plain text invoice.
It won't look as nice, but it will save almost a hundred lines of code.
Most of those lines are PDF positioning statements.  Even so, these routines
are rather large and are largely uninteresting.
So that the link structure will not break, I'm still calling the method
\verb+do_pdf+\index{do pdf@\verb+do_pdf+}\index{billing
example!generating invoices}.
Feel free to change the links, etc.

{\footnotesize
\begin{verbatim}
 1 #------------------------------------------------------------
 2 # $self->do_pdf( $id )
 3 #------------------------------------------------------------
 4 sub do_pdf {
 5     my ( $self, $id ) = @_;
 6 
 7     # pull variables out of invoice row ready for here doc
 8     my $invoice     = $INVOICE->gfind( $self, $id );
 9     my $invoice_num = $invoice->number;
10     my $sent        = $invoice->sent;
11     my $description = $invoice->description || '';
12 
13     $description    = "\n$description\n" if $description;
14 
15     # my company data
16     my %corp_data;
17 
18     foreach my $column qw(
19             name address city state zip contact_phone
20     ) {
21         $corp_data{ $column } =
22                 $invoice->my_company->$column();
23     }
24 
25     # customer data
26     my %cust_data;
27     foreach my $column qw(
28             name address city state zip
29             contact_phone contact_name
30     ) {
31         $cust_data{ $column } =
32                 $invoice->customer->$column();
33     }
34 
35     # tasks, pass the buck
36     my ( $task_output, $total ) =
37             $self->_task_output( $id );
38 
39     my $retval = << "EO_Invoice";
40 Billed By:
41 $corp_data{ name }
42 $corp_data{ address }
43 $corp_data{ city }, $corp_data{ state } $corp_data{ zip }
44 $corp_data{ contact_phone }
45 
46 Billed To:
47 $cust_data{ name }
48 $cust_data{ address }
49 $cust_data{ city }, $cust_data{ state } $cust_data{ zip }
50 $cust_data{ contact_phone }
51 Attn: $cust_data{ contact_name }
52 
53 Invoice Number: $invoice_num Invoice Date: $sent $description
54 
55 Date         Hours    Rate/hr    Total   Task
56 $task_output
57 _____________________________________________________________
58 
59 Total Amount Due: $total
60 
61 Invoice due upon receipt.
62 EO_Invoice
63 
64     $self->template_disable( 1 ); 		# turn off templating
65     $self->content_type( 'text/plain' );
66 
67     return $retval;
68 }
\end{verbatim}
}

This generates all of the output except the actual task list, which is below.
First, on line 8, I retrieve the invoice to be made printable, using the
\verb+gfind+\index{gfind@\verb+gfind+ ORM sugar
method}\index{ORM!sugar!gfind@\verb+gfind+} sugar
method provided by Gantry's DBIx::Class base module.  From it I fish out
various strings to go on the bill.  I want to fish first instead of trying
to call methods inside the here doc, since calling code in a double quoted
string in Perl 5 is tedious at best.
The tasks are presented via their own method:

{\footnotesize
\begin{verbatim}
sub _task_output {
    my ( $self, $id ) = @_;

    my @tasks = $LINE_ITEM->gsearch(
            $self, { invoice => $id }
    );

    my @rows;
    my $total       = 0;
    my $space       = ' ';

    foreach my $task ( @tasks ) {
        my $row_amount = $task->hours() *
                         $task->charge_per_hour();

        $total        += $row_amount;

        my $row = $task->due_date()        . $space x 4;
        $row   .= $task->hours()           . $space x 8;
        $row   .= $task->charge_per_hour() . $space x 9;
        $row   .= $row_amount              . $space x 10;
        $row   .= $task->name();

        push @rows, $row;
    }
    my $task_output = join "\n", @rows;
    
    return $task_output, $total;
}
\end{verbatim}
}

Since each task will go on one line, the fishing can be done directly into
the output.  Note that the cost of each line item is calculated here and
a total is returned along with the text for each line item.

Now, when the user clicks Tasks on the invoice page, they will go to the
Line Item controller, but see only the tasks for that invoice.  When the
user clicks PDF, they will get a new page with the text ready for inclusion
in a sickly looking email.  The genuine PDF version looks better.

\section*{Summary}

Keep in mind that all preceding code was written for
programmer efficiency\index{programmer efficiency defended}
only.  Most of the code could be written to minimize other costs, like
run time.  If the app is successful, expect to be required to make such
changes over time.  But, I always start with getting something that works,
which is easy to read.  Only after a speed problem emerges do I shift from
optimizing for my time to optimizing for the computer's time.

In this chapter, we have seen how to customize a Bigtop generated app with
a minimum of new code.  While that may not be the best use a computer's
time for large apps, it is quite a good use of our time for most apps.

The next chapter presents another case study beginning with Bigtop and ending
with custom code.  This time, concepts such as Apache basic auth and
many-to-many relationships between tables appear.
